package name.luoyong.shiro.shiroIniCustom.filter;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayDeque;
import java.util.Deque;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import name.luoyong.shiro.shiroIniCustom.listener.SessionMap;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.subject.Subject;

public class KickOutFilter extends AbstractFilter {
	
	private static final String StrKickOut="isKickOut";
	private static final String first = "first";
	private static final String last = "last";
	private static final String DefaultKickOutUrl = "/login";
	
	private Cache<String, Deque<String>> kickOutCache = null;
	
	
	
	// param
	private String kickOutUrl = DefaultKickOutUrl;
	private int size = 1;
	private String kickOutCacheName = "kickOutCache";
	private String kickPosition = first;
	
	
	public String getKickOutUrl() {
		return kickOutUrl;
	}

	public void setKickOutUrl(String kickOutUrl) {
		this.kickOutUrl = kickOutUrl;
	}

	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}
	
	
	public String getKickPosition() {
		return kickPosition;
	}

	public void setKickPosition(String kickPosition) {
		this.kickPosition = kickPosition;
	}
	
	// core 

	@Override
	protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		
		if(kickOutCache==null)
			kickOutCache = cacheManager.getCache(kickOutCacheName);
		
		Subject subject = SecurityUtils.getSubject();
		String username = (String)subject.getPrincipal();
		HttpSession session = request.getSession();
		String sessionId =  session.getId();
		
		if(!subject.isAuthenticated() && !subject.isRemembered()) {
			chain.doFilter(request, response);
			return;
		}
		
		Deque<String> sessionIds = getSessionIds(username);
		
		boolean isContain = sessionIds.contains(sessionId);
		if(isContain) {
			chain.doFilter(request, response);
			return;
		}
		
		boolean kicked = tryKickOutThis(subject, request, response);
		if(kicked) {
			return;
		}
		
		sessionIds.add(sessionId);
		
		markKickOut(sessionIds, request, response);
		
		kicked = tryKickOutThis(subject,request, response);
		if(kicked) {
			return;
		}
		
		chain.doFilter(request, response);
	}
	
	private Deque<String> getSessionIds(String username) {
		Deque<String> deque = kickOutCache.get(username);
		
		if(deque == null) {
			deque = new ArrayDeque<String>();
			kickOutCache.put(username, deque);
		}
		return deque;
	}
	
	private void markKickOut(Deque<String> sessionIds, HttpServletRequest request, HttpServletResponse response) {
		while(sessionIds.size()>size) {
			String kickoutSessionId = null;
			if(kickPosition.equals(first)) {
				kickoutSessionId = sessionIds.removeFirst();
			} else {
				kickoutSessionId = sessionIds.removeLast();
			}
			
			HttpSession kickoutSession = SessionMap.get(kickoutSessionId);
			if(kickoutSession != null) {
				kickoutSession.setAttribute(StrKickOut, true);
			}
		}
	}
	
	private boolean shouldKickOut(HttpSession session) {
		boolean isKickOut = false;
		Object isKickOutObject = session.getAttribute(StrKickOut);
		
		if(isKickOutObject != null) {
			isKickOut = (Boolean) isKickOutObject;
		}
		return isKickOut;
	}
	
	private boolean tryKickOutThis(Subject subject, HttpServletRequest request, HttpServletResponse response) throws IOException {
		HttpSession session = request.getSession();
		String uri = request.getRequestURI();
		if(shouldKickOut(session)) {
			subject.logout();
			String param = "?errorMsg="+URLEncoder.encode("你已经在别处登陆，请重新登陆。","UTF-8")+"&callbackUrl="+URLEncoder.encode(uri, "UTF-8");
			response.sendRedirect(kickOutUrl+param);
			return true;
		}
		
		return false;
	}
	
}
